﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.OleDb;
using System.Data;
using System.Text.RegularExpressions;
using System.IO;
using System.Diagnostics;

namespace ElToolAttachmentGrabber
{
	class Program
	{
		/*
		 * You'll definitely need to hack these constants here
		 */
		private const string pathToCurlExe = @"C:\path\to\curl\curl.exe";
      private const string eltoolUserName = "yourusername";
		private const string eltoolPassword = "yourpassword";
		private const string eltoolAccountName = "youraccountname";
		private const string outputDirectory = @"C:\Users\yourname\Documents\ElToolAttachmentDownloads";

		static void Main(string[] args)
		{
			/*
			 * Eltool only allows you to download an MDB version of your tickets. Because I was migrating to Redmine,
			 * I migrated my data to MySql. The code below assumes that is the case, but it's fairly easily changed if
			 * you want to point it directly at the dowvloaded access MDB file instead.
			 */

			// TODO: If you are pointing at an Access database for your source data, the factory below must be initialized with an Oledb factory instead.
			System.Data.Common.DbProviderFactory dataFactory =
				new MySql.Data.MySqlClient.MySqlClientFactory();
			// TODO: If you are pointing at an Access database, you will need to change the connection string and queries below slightly.
			string cnString = "server=mysqlserver; database=bitnami_redmine; user id=mysqluser; password=mysqlpassword;";
			// This code intentionally grabs only tickets with attachments. You could change this behavior by NOT joining on the attachments table below.
			string getAttachmentsInfoCommandText =
				"select il.`issue number` from elementool.issuelist il " +
				"inner join elementool.attachments a on a.`Issue ID` = il.`Issue Number`;" +
				"select * from elementool.attachments;";
			
			/*
			 * You should not need to hack (config-wise, at least) code below this point.
			 * There are lots of hard-coded strings, but they're totally defined by elementool.
			 * If these items are changed by them, it's likely a very big honkin' deal that
			 * would require significant changes to this code anyway.
			 */

			string attachmentDestinationDirectory = Path.Combine(outputDirectory, "Attachments");

			// Get the issues and attachments
			var issues = Issue.GetIssuesAndAttachments(dataFactory, cnString, getAttachmentsInfoCommandText);

			// log in to eltool
			Dictionary<string, string> loginParams = new Dictionary<string, string>();
			loginParams.Add("actiontype", "");
         loginParams.Add("accountname", eltoolAccountName);
			loginParams.Add("username", eltoolUserName);
			loginParams.Add("password", eltoolPassword);
			string eltoolLoginUrl = "http://www.elementool.com/Services/loginPage/enter.aspx?source=f";
			string outputPath = Path.Combine(outputDirectory, "loginResult.htm");
			CurlCommand.Execute(true, pathToCurlExe, eltoolLoginUrl, loginParams, outputPath);

			// At this point, a file called loginResult.htm has been written to the file system. We'll parse useful info out of that file.


			// Eltool uses four "serial" numbers in some way that identifies the user's session. These are generated
			// during the login sequence and then must be passed via the querystring on all subsequent requests.

			// Get the serials for our current session
			var serialz = new string[4];
			var serialRegex = new Regex(
				@"<input type=""hidden"" name=""(?<serialKey>serial\d)"" id=""serial\d"" value=""(?<value>\w+)"" />",
				RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
			string fileContent = null;
			using (StreamReader strr = new StreamReader(outputPath))
			{
				fileContent = strr.ReadToEnd();
			}
			var serialMatches = serialRegex.Matches(fileContent);
			int i = 0;
			foreach (Match match in serialMatches)
			{
				serialz[i] = match.Groups[2].Value;
				i++;
			}

			// Used to parse individual file attachment URL's out of the ticket's HTML contents.
			Regex attachmentUrlRegex = new Regex(
				@"(?<url>FileSharing\/GetFile[^""]+)"" target=""_blank"">(?<filename>[^<]+)</a>",
				RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);

			foreach (var issue in issues)
			{
				Trace.WriteLine(string.Format("Processing issue {0}", issue.ID));
				Trace.Indent();

				// Get the output for this issue
				outputPath = Path.Combine(outputDirectory, string.Format("Ticket{0}.htm", issue.ID));
				StringBuilder ticketUrl = new StringBuilder(
					string.Format("http://www.elementool.com/Services/BugTracking/ViewIssue.aspx?bug_no={0}&actionType=display", 
					issue.ID));
				for (int serialCounter = 0; serialCounter < serialz.Length; serialCounter++)
				{
					ticketUrl.AppendFormat("&serial{0}={1}", (serialCounter + 1), serialz[serialCounter]);
				}
				Trace.WriteLine("Getting ticket contents");
				CurlCommand.Execute(false, pathToCurlExe, ticketUrl.ToString(), new Dictionary<string, string>(), outputPath);

				// Get the filenames and URL's out of the ticket contents
				using (StreamReader srdr = new StreamReader(outputPath))
				{
					fileContent = srdr.ReadToEnd();
				}
				var fileMatches = attachmentUrlRegex.Matches(fileContent);
				foreach (Match m in fileMatches)
				{
					Trace.Indent();
					Trace.WriteLine("Processing attachment.");
					var attachmentUrl = m.Groups[1].Value;
					var fileName = m.Groups[2].Value;

					var matchingAttachmentEntity =
						issue.Attachments.Where(att => att.FileName.EndsWith("\\" + fileName, StringComparison.CurrentCultureIgnoreCase))
						.FirstOrDefault();
					if (matchingAttachmentEntity == null)
					{
						Console.WriteLine(
							String.Format("Ticket {0} - no matching attachment found in entity from database for {1}",
							issue.ID, fileName));
					}
					string attachmentDestinationPath = Path.Combine(attachmentDestinationDirectory, matchingAttachmentEntity.FileName.Replace("\\", "_"));
					
					// HACK: the ampersand replacement below is a bit buggy if the filename uploaded to eltool has an ampersand in it.
					string fullAttachmentUrl = String.Format("http://www.elementool.com/Services/{0}", attachmentUrl).Replace("&amp;", "&");
					
					// == This MAY be all that's required to fix it , but I didn't test it. ==
					//string fullAttachmentUrl = String.Format("http://www.elementool.com/Services/{0}", attachmentUrl.Replace("&amp;", "&"));
					
					Trace.WriteLine(String.Format("Getting attachment file, saving to {0}", attachmentDestinationPath));
					CurlCommand.Execute(false, pathToCurlExe, fullAttachmentUrl, new Dictionary<string, string>(), attachmentDestinationPath);
					Trace.Unindent();
				}
				Trace.Unindent();
			}

			Console.WriteLine("Complete.");
			Console.ReadKey();
		}
	}
}
